package assign4;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Iterator;
import java.util.Vector;

/** A distributed server that runs in a centralized fashion.
 * All servers maintain redundant data, and the entire system
 * should maintain a coherent database throughout it's lifetime.
 * 
 * - Multiple reads can occur on a single table simultaneously
 * - A single write can occur on a single table at any instant
 * - Multiple writes can occur on separate tables simultaneously,
 *     as can multiple reads.
 */
public class ReplicatedDBServer extends UnicastRemoteObject implements IRemoteDatabase
{
	private static final long serialVersionUID = 1L;
	private int numTables = Globals.numOfDatabaseTables;
	
	private boolean master = false;
	private int[] dataTables = new int[numTables];
	IRemoteDatabase masterServer;
	
	//Master server data
	Vector<DatabaseLock> locks;
	Vector<IRemoteDatabase> slaveServers;

	protected ReplicatedDBServer(String name, String masterServerUri) throws Exception
	{
		super();
		
		Policy.init(ReplicatedDBServer.class);
		Naming.rebind(name, this);
		
		Registry registry = LocateRegistry.getRegistry(Globals.rmiRegistryHost, Globals.rmiRegistryPort);
		masterServer = (IRemoteDatabase) registry.lookup(Globals.masterServerName);
		
		// Decide if this server is to be the master
		if(name.compareTo(Globals.masterServerName) == 0)
		{
			master = true;
			
			slaveServers = new Vector<IRemoteDatabase>();
			locks = new Vector<DatabaseLock>();
			for(int i=0; i<numTables; ++i)
			{
				locks.add(new DatabaseLock());
			}
		}
		else
		{
			master = false;
			masterServer.registerSlave(Globals.serverNamePrefix + name);
		}
		
		System.out.println(name + " is running.");
	}
	
	@Override
	// performs an atomic read operation
	public int read(int table) throws RemoteException
	{
		masterServer.startRead(table);
		int dataToRet = dataTables[table];
		masterServer.endRead(table);
		return dataToRet;
	}
	
	@Override
	// performs an atomic write operation
	public void write(int val, int table) throws RemoteException
	{
		masterServer.startWrite(table);
		masterServer.commitWrite(val, table);
		masterServer.endWrite(table);
	}
	
	@Override
	// called by the master server on the slaves to update their local database
	public void remoteWrite(int dataToWrite, int table) throws RemoteException
	{
		dataTables[table] = dataToWrite;
	}
	
	
	//
	// Master Server only Functions Below
	//
	
	@Override
	public void commitWrite(int dataToCommit, int table) throws RemoteException
	{
		dataTables[table] = dataToCommit;
		Iterator<IRemoteDatabase> slaveServer_It = slaveServers.iterator();
		while(slaveServer_It.hasNext())
		{
			IRemoteDatabase curDbImpl = slaveServer_It.next();
			
			try{
				curDbImpl.remoteWrite(dataTables[table], table);
			} catch(RemoteException e) {
				e.printStackTrace();
			}
		}
	}
	
	// Called slave servers before they read or write to their requested table
	public void startRead(int table) throws RemoteException
	{
		locks.get(table).startRead();
	}

	public void endRead(int table) throws RemoteException
	{
		locks.get(table).endRead();
	}
	
	public void startWrite(int table) throws RemoteException
	{
		locks.get(table).startWrite();
	}
	
	public void endWrite(int table) throws RemoteException
	{
		locks.get(table).endWrite();
	}

	@Override
	synchronized public boolean registerSlave(String slaveServerUri) throws RemoteException
	{
		if(master != true)
		{
			return false;
		}
		
		try
		{
			IRemoteDatabase slaveServerToAdd = (IRemoteDatabase) Naming.lookup(slaveServerUri);
			slaveServers.add(slaveServerToAdd);
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		
		return false;
	}
}